/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 * USA.
 *
 * =======================================================================
 *
 * This file implements the generic part of the menu
 *
 * =======================================================================
 */

#include <string.h>
#include <ctype.h>
#include "../header/client.h"
#include "header/qmenu.h"

static void	 Action_DoEnter( menuaction_s *a );
static void	 Action_Draw( menuaction_s *a );
static void  Menu_DrawStatusBar( const char *string );
static void	 MenuList_Draw( menulist_s *l );
static void	 Separator_Draw( menuseparator_s *s );
static void	 Slider_DoSlide( menuslider_s *s, int dir );
static void	 Slider_Draw( menuslider_s *s );
static void	 SpinControl_Draw( menulist_s *s );
static void	 SpinControl_DoSlide( menulist_s *s, int dir );

#define RCOLUMN_OFFSET  16
#define LCOLUMN_OFFSET -16

extern refexport_t re;
extern viddef_t viddef;

#define VID_WIDTH  viddef.width
#define VID_HEIGHT viddef.height
#define Draw_Char re.DrawChar
#define Draw_Fill re.DrawFill

void Action_DoEnter( menuaction_s *a ) {
	if ( a->generic.callback )
		a->generic.callback( a );
}

void Action_Draw( menuaction_s *a ) {
	if ( a->generic.flags & QMF_LEFT_JUSTIFY ) {
		if ( a->generic.flags & QMF_GRAYED )
			Menu_DrawStringDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );

		else
			Menu_DrawString( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );

	} else {
		if ( a->generic.flags & QMF_GRAYED )
			Menu_DrawStringR2LDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );

		else
			Menu_DrawStringR2L( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name );
	}

	if ( a->generic.ownerdraw )
		a->generic.ownerdraw( a );
}

qboolean Field_DoEnter( menufield_s *f ) {
	if ( f->generic.callback ) {
		f->generic.callback( f );
		return true;
	}

	return false;
}

void Field_Draw( menufield_s *f ) {
	int i;
	char tempbuffer[128]="";

	if ( f->generic.name )
		Menu_DrawStringR2LDark( f->generic.x + f->generic.parent->x + LCOLUMN_OFFSET, f->generic.y + f->generic.parent->y, f->generic.name );

	strncpy( tempbuffer, f->buffer + f->visible_offset, f->visible_length );

	Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y - 4, 18 );
	Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y + 4, 24 );

	Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y - 4, 20 );
	Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y + 4, 26 );

	for ( i = 0; i < f->visible_length; i++ ) {
		Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y - 4, 19 );
		Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y + 4, 25 );
	}

	Menu_DrawString( f->generic.x + f->generic.parent->x + 24, f->generic.y + f->generic.parent->y, tempbuffer );

	if ( Menu_ItemAtCursor( f->generic.parent ) == f ) {
		int offset;

		if ( f->visible_offset )
			offset = f->visible_length;

		else
			offset = f->cursor;

		if ( ( ( int ) ( Sys_Milliseconds() / 250 ) ) & 1 ) {
			Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8,
			           f->generic.y + f->generic.parent->y,
			           11 );

		} else {
			Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8,
			           f->generic.y + f->generic.parent->y,
			           ' ' );
		}
	}
}

extern int keydown[];

qboolean Field_Key( menufield_s *f, int key ) {
	switch ( key ) {
		case K_KP_SLASH:
			key = '/';
			break;
		case K_KP_MINUS:
			key = '-';
			break;
		case K_KP_PLUS:
			key = '+';
			break;
		case K_KP_HOME:
			key = '7';
			break;
		case K_KP_UPARROW:
			key = '8';
			break;
		case K_KP_PGUP:
			key = '9';
			break;
		case K_KP_LEFTARROW:
			key = '4';
			break;
		case K_KP_5:
			key = '5';
			break;
		case K_KP_RIGHTARROW:
			key = '6';
			break;
		case K_KP_END:
			key = '1';
			break;
		case K_KP_DOWNARROW:
			key = '2';
			break;
		case K_KP_PGDN:
			key = '3';
			break;
		case K_KP_INS:
			key = '0';
			break;
		case K_KP_DEL:
			key = '.';
			break;
	}

	if ( key > 127 ) {
		switch ( key ) {
			case K_DEL:
			default:
				return false;
		}
	}

	switch ( key ) {
		case K_KP_LEFTARROW:
		case K_LEFTARROW:
		case K_BACKSPACE:

			if ( f->cursor > 0 ) {
				memmove( &f->buffer[f->cursor-1], &f->buffer[f->cursor], strlen( &f->buffer[f->cursor] ) + 1 );
				f->cursor--;

				if ( f->visible_offset ) {
					f->visible_offset--;
				}
			}

			break;

		case K_KP_DEL:
		case K_DEL:
			memmove( &f->buffer[f->cursor], &f->buffer[f->cursor+1], strlen( &f->buffer[f->cursor+1] ) + 1 );
			break;

		case K_KP_ENTER:
		case K_ENTER:
		case K_ESCAPE:
		case K_TAB:
			return false;

		case K_SPACE:
		default:

			if ( !isdigit( key ) && ( f->generic.flags & QMF_NUMBERSONLY ) )
				return false;

			if ( f->cursor < f->length ) {
				f->buffer[f->cursor++] = key;
				f->buffer[f->cursor] = 0;

				if ( f->cursor > f->visible_length ) {
					f->visible_offset++;
				}
			}
	}

	return true;
}

void Menu_AddItem( menuframework_s *menu, void *item ) {
	if ( menu->nitems == 0 )
		menu->nslots = 0;

	if ( menu->nitems < MAXMENUITEMS ) {
		menu->items[menu->nitems] = item;
		( ( menucommon_s * ) menu->items[menu->nitems] )->parent = menu;
		menu->nitems++;
	}

	menu->nslots = Menu_TallySlots( menu );
}

/*
 * This function takes the given menu, the direction, and attempts
 * to adjust the menu's cursor so that it's at the next available
 * slot.
 */
void Menu_AdjustCursor( menuframework_s *m, int dir ) {
	menucommon_s *citem;

	/* see if it's in a valid spot */
	if ( m->cursor >= 0 && m->cursor < m->nitems ) {
		if ( ( citem = Menu_ItemAtCursor( m ) ) != 0 ) {
			if ( citem->type != MTYPE_SEPARATOR )
				return;
		}
	}

	/* it's not in a valid spot, so crawl in the direction
	   indicated until we find a valid spot */
	if ( dir == 1 ) {
		while ( 1 ) {
			citem = Menu_ItemAtCursor( m );

			if ( citem )
				if ( citem->type != MTYPE_SEPARATOR )
					break;

			m->cursor += dir;

			if ( m->cursor >= m->nitems )
				m->cursor = 0;
		}

	} else {
		while ( 1 ) {
			citem = Menu_ItemAtCursor( m );

			if ( citem )
				if ( citem->type != MTYPE_SEPARATOR )
					break;

			m->cursor += dir;

			if ( m->cursor < 0 )
				m->cursor = m->nitems - 1;
		}
	}
}

void Menu_Center( menuframework_s *menu ) {
	int height;

	height = ( ( menucommon_s * ) menu->items[menu->nitems-1])->y;
	height += 10;

	menu->y = ( VID_HEIGHT - height ) / 2;
}

void Menu_Draw( menuframework_s *menu ) {
	int i;
	menucommon_s *item;

	/* draw contents */
	for ( i = 0; i < menu->nitems; i++ ) {
		switch ( ( ( menucommon_s * ) menu->items[i] )->type ) {
			case MTYPE_FIELD:
				Field_Draw( ( menufield_s * ) menu->items[i] );
				break;
			case MTYPE_SLIDER:
				Slider_Draw( ( menuslider_s * ) menu->items[i] );
				break;
			case MTYPE_LIST:
				MenuList_Draw( ( menulist_s * ) menu->items[i] );
				break;
			case MTYPE_SPINCONTROL:
				SpinControl_Draw( ( menulist_s * ) menu->items[i] );
				break;
			case MTYPE_ACTION:
				Action_Draw( ( menuaction_s * ) menu->items[i] );
				break;
			case MTYPE_SEPARATOR:
				Separator_Draw( ( menuseparator_s * ) menu->items[i] );
				break;
		}
	}

	item = Menu_ItemAtCursor( menu );

	if ( item && item->cursordraw ) {
		item->cursordraw( item );

	} else if ( menu->cursordraw ) {
		menu->cursordraw( menu );

	} else if ( item && item->type != MTYPE_FIELD ) {
		if ( item->flags & QMF_LEFT_JUSTIFY ) {
			Draw_Char( menu->x + item->x - 24 + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) );

		} else {
			Draw_Char( menu->x + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) );
		}
	}

	if ( item ) {
		if ( item->statusbarfunc )
			item->statusbarfunc( ( void * ) item );

		else if ( item->statusbar )
			Menu_DrawStatusBar( item->statusbar );

		else
			Menu_DrawStatusBar( menu->statusbar );

	} else {
		Menu_DrawStatusBar( menu->statusbar );
	}
}

void Menu_DrawStatusBar( const char *string ) {
	if ( string ) {
		int l = (int)strlen( string );
		int maxcol = VID_WIDTH / 8;
		int col = maxcol / 2 - l / 2;

		Draw_Fill( 0, VID_HEIGHT-8, VID_WIDTH, 8, 4 );
		Menu_DrawString( col*8, VID_HEIGHT - 8, string );

	} else {
		Draw_Fill( 0, VID_HEIGHT-8, VID_WIDTH, 8, 0 );
	}
}

void Menu_DrawString( int x, int y, const char *string ) {
	unsigned i;

	for ( i = 0; i < strlen( string ); i++ ) {
		Draw_Char( ( x + i*8 ), y, string[i] );
	}
}

void Menu_DrawStringDark( int x, int y, const char *string ) {
	unsigned i;

	for ( i = 0; i < strlen( string ); i++ ) {
		Draw_Char( ( x + i*8 ), y, string[i] + 128 );
	}
}

void Menu_DrawStringR2L( int x, int y, const char *string ) {
	unsigned i;

	for ( i = 0; i < strlen( string ); i++ ) {
		Draw_Char( ( x - i*8 ), y, string[strlen(string)-i-1] );
	}
}

void Menu_DrawStringR2LDark( int x, int y, const char *string ) {
	unsigned i;

	for ( i = 0; i < strlen( string ); i++ ) {
		Draw_Char( ( x - i*8 ), y, string[strlen(string)-i-1]+128 );
	}
}

void *Menu_ItemAtCursor( menuframework_s *m ) {
	if ( m->cursor < 0 || m->cursor >= m->nitems )
		return 0;

	return m->items[m->cursor];
}

qboolean Menu_SelectItem( menuframework_s *s ) {
	menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s );

	if ( item ) {
		switch ( item->type ) {
			case MTYPE_FIELD:
				return Field_DoEnter( ( menufield_s * ) item ) ;
			case MTYPE_ACTION:
				Action_DoEnter( ( menuaction_s * ) item );
				return true;
			case MTYPE_LIST:
				return false;
			case MTYPE_SPINCONTROL:
				return false;
		}
	}

	return false;
}

void Menu_SetStatusBar( menuframework_s *m, const char *string ) {
	m->statusbar = string;
}

void Menu_SlideItem( menuframework_s *s, int dir ) {
	menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s );

	if ( item ) {
		switch ( item->type ) {
			case MTYPE_SLIDER:
				Slider_DoSlide( ( menuslider_s * ) item, dir );
				break;
			case MTYPE_SPINCONTROL:
				SpinControl_DoSlide( ( menulist_s * ) item, dir );
				break;
		}
	}
}

int Menu_TallySlots( menuframework_s *menu ) {
	int i;
	int total = 0;

	for ( i = 0; i < menu->nitems; i++ ) {
		if ( ( ( menucommon_s * ) menu->items[i] )->type == MTYPE_LIST ) {
			int nitems = 0;
			const char **n = ( ( menulist_s * ) menu->items[i] )->itemnames;

			while (*n)
				nitems++, n++;

			total += nitems;

		} else {
			total++;
		}
	}

	return total;
}

void MenuList_Draw( menulist_s *l ) {
	const char **n;
	int y = 0;

	Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y, l->generic.name );

	n = l->itemnames;

	Draw_Fill( l->generic.x - 112 + l->generic.parent->x, l->generic.parent->y + l->generic.y + l->curvalue*10 + 10, 128, 10, 16 );

	while ( *n ) {
		Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y + y + 10, *n );

		n++;
		y += 10;
	}
}

void Separator_Draw( menuseparator_s *s ) {
	if ( s->generic.name )
		Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->generic.name );
}

void Slider_DoSlide( menuslider_s *s, int dir ) {
	s->curvalue += dir;

	if ( s->curvalue > s->maxvalue )
		s->curvalue = s->maxvalue;

	else if ( s->curvalue < s->minvalue )
		s->curvalue = s->minvalue;

	if ( s->generic.callback )
		s->generic.callback( s );
}

#define SLIDER_RANGE 10

void Slider_Draw( menuslider_s *s ) {
	int	i;

	Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET,
	                        s->generic.y + s->generic.parent->y,
	                        s->generic.name );

	s->range = ( s->curvalue - s->minvalue ) / ( float ) ( s->maxvalue - s->minvalue );

	if ( s->range < 0)
		s->range = 0;

	if ( s->range > 1)
		s->range = 1;

	Draw_Char( s->generic.x + s->generic.parent->x + RCOLUMN_OFFSET, s->generic.y + s->generic.parent->y, 128);

	for ( i = 0; i < SLIDER_RANGE; i++ )
		Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 129);

	Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 130);
	Draw_Char( ( int ) ( 8 + RCOLUMN_OFFSET + s->generic.parent->x + s->generic.x + (SLIDER_RANGE-1)*8 * s->range ), s->generic.y + s->generic.parent->y, 131);
}

void SpinControl_DoSlide( menulist_s *s, int dir ) {
	s->curvalue += dir;

	if ( s->curvalue < 0 )
		s->curvalue = 0;

	else if ( s->itemnames[s->curvalue] == 0 )
		s->curvalue--;

	if ( s->generic.callback )
		s->generic.callback( s );
}

void SpinControl_Draw( menulist_s *s ) {
	char buffer[100];

	if ( s->generic.name ) {
		Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET,
		                        s->generic.y + s->generic.parent->y,
		                        s->generic.name );
	}

	if ( !strchr( s->itemnames[s->curvalue], '\n' ) ) {
		Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->itemnames[s->curvalue] );

	} else {
		strcpy( buffer, s->itemnames[s->curvalue] );
		*strchr( buffer, '\n' ) = 0;
		Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, buffer );
		strcpy( buffer, strchr( s->itemnames[s->curvalue], '\n' ) + 1 );
		Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y + 10, buffer );
	}
}

